package service.inv;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import socket.CmdMessageGrpIds;
import socket.CmdMsgType;
import socket.UDP;
import struct.CheckInfo;
import struct.InvServiceConfig;
import struct.InvServiceResult;
import struct.SensorData;
import struct.enums.CheckResult;
import struct.enums.SensorDataType;
import struct.sync.SynchronousJsonObject;
import struct.sync.SynchronousSensorData;
import struct.sync.SynchronousString;
import util.Util;
import appmgr.AppMgr;
import pubsub.Channel;
import pubsub.Publisher;
import resource.SensorMgr;

import java.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;

public class AppInvServer{
    private final AppMgr appMgr;
    private final Set<String> monitoredObjs = new HashSet<>();
    private final SynchronousJsonObject checkChannel = new SynchronousJsonObject();
    private final Map<String, InvLine> lines = new HashMap<>();
    private final InvServiceConfig config;
    private final SynchronousSensorData _invGetSensorData = new SynchronousSensorData();

    public AppInvServer(AppMgr appMgr, InvServiceConfig config) {
        this.appMgr = appMgr;
        this.config = config;
    }

    public SynchronousSensorData getInvGetSensorData() {
        return _invGetSensorData;
    }

    public void start() {
        File dir = new File("output/platform/inv/" + appMgr.getAppName());
        File oldDir = new File("output_old/platform/inv/" + appMgr.getAppName());
        Util.copyDirectory(dir, oldDir);
        Util.deleteDir(dir);
        dir.mkdirs();
    }

    public InvServiceConfig getConfig() {
        return config;
    }

    public InvServiceResult getResult() {
        //TODO
        Map<String, Object> lines = new HashMap<>();
        for (Map.Entry<String, InvLine> entry : this.lines.entrySet()) {
            lines.put(entry.getKey(), entry.getValue().simpleToJSON());
        }
        return new InvServiceResult(lines);
    }

    public boolean monitor(List<String> objs) {
        monitoredObjs.addAll(objs);
        return true;
    }

    public boolean isMonitored(List<String> objs) {
        return monitoredObjs.containsAll(objs);
    }

    public boolean check(JSONObject jo) {
        //TODO
        CompletableFuture.runAsync(() -> {
            String lineNumber = jo.getString(CheckInfo.LINE_NUMBER_FIELD);
            int lineId = Integer.parseInt(lineNumber.substring(lineNumber.indexOf("[") + 1, lineNumber.indexOf("]")));
            long checkTime = jo.getLongValue(CheckInfo.CHECK_TIME_FIELD);
            int iterId = jo.getIntValue(CheckInfo.ITER_ID_FIELD);
            JSONObject objs = jo.getJSONObject("objs");

            if (!lines.containsKey(lineNumber)) {
                String[] checkNames = objs.keySet().toArray(new String[0]);
                lines.put(lineNumber, new InvLine(config, checkNames, appMgr.getAppName(), lineId));
            }

            InvLine line = lines.get(lineNumber);
            //TODO:获取环境上下文
            List<Double> tmp = new ArrayList<>();
            CmdMessageGrpIds send = new CmdMessageGrpIds(CmdMsgType.SENSORY_REQUEST, null, List.of(appMgr.getGrpId()));
            for (SensorMgr config : appMgr.getSensors()) {
                String sensorName = config.getSensorName();
                if (!appMgr.getRequestMap().containsKey(Channel.getSensor(sensorName))) {
                    appMgr.getRequestMap().put(Channel.getSensor(sensorName), new SynchronousString());
                }
                appMgr.getRequestMap().get(Channel.getSensor(sensorName)).put("invGetSensorData");
                Publisher.publish(Channel.getSensorRequest(sensorName), send.toString());
                SensorData sensorData = _invGetSensorData.blockTake();
                for (Object v : sensorData.getAllData().values()) {
                    //把v转换成double
                    try {
                        tmp.add(Double.parseDouble(v.toString()));
                    } catch (Exception e) {
                        tmp.add(Util.convertToDouble(v.toString()));
                    }
                }
            }
            double[] envCtxVals = tmp.stream().mapToDouble(Double::doubleValue).toArray();
            CheckResult result = line.check(new InvData(envCtxVals, JSON.parseObject(objs.toJSONString(),
                    new TypeReference<Map<String, Double>>(){})));

            //TODO:返回checkInfo给appDriver并传递给app
            JSONObject joo = new JSONObject();
            joo.put(SensorData.SENSOR_DATA_TYPE_FIELD, SensorDataType.INV_REPORT);
            joo.put(CheckInfo.INV_NAME_FIELD, "INV_REPORT" + lineId);
            joo.put(CheckInfo.LINE_NUMBER_FIELD, lineId);
            joo.put(CheckInfo.ITER_ID_FIELD, iterId);
            joo.put(CheckInfo.CHECK_TIME_FIELD, checkTime);
            joo.put(CheckInfo.INV_RESULT_FIELD, result);
            JSONObject jooo = new JSONObject(2);
            jooo.put("channel", "INV_REPORT" + lineId);
            jooo.put("msg", joo);
            UDP.send(appMgr.getAppDriver().getClientIP(), appMgr.getAppDriver().getClientUDPPort(), jooo.toJSONString());
            appMgr.getAppDriver().getLogger().info(String.format("[Platform -> %s][UDP]: %s", appMgr.getAppName(), jooo.toJSONString()));
        });
        return true;
    }

    public JSONObject save() {
        JSONObject jo = new JSONObject();
        for (Map.Entry<String, InvLine> entry : lines.entrySet()) {
            jo.put(entry.getKey(), entry.getValue().fullToJSON());
        }
        return jo;
    }

    public boolean load(JSONObject jo) {
        try {
            for (Map.Entry<String, Object> entry : jo.entrySet()) {
                lines.put(entry.getKey(), InvLine.fromJSON((JSONObject) entry.getValue()));
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean checkGenerated() {
        boolean ret = true;
        for (InvLine line : lines.values()) {
            for (InvGroup group : line.invGroups) {
                if (group.invs == null) {
                    ret = false;
                    break;
                }
            }
        }
        return ret;
    }
}
